#include "ai.h"
#include <stdlib.h>

typedef struct {

	Move move;
	float eval;
	
} MoveEvaluation;

/**********************************************************
 * limit ruchów do przodu                                 *
 * ustawione 4: ruch, odpowiedź, ruch, odpowiedź          *
 * większe parzyste wartości powodują zamulanie programu, *
 * a nieparzyste - totalne wypaczenie, ponieważ wynik     *
 * zawsze wydaje się być dobry							  *
 **********************************************************/
static const int LIMIT = 4;

/*************************************************************
 * limit ruchów sprawdzanych (losowo) na każdym poziomie     *
 * (jest dostosowywany przy każdym wywołaniu proporcjonalnie *
 * do wielkości planszy                                      *
 *************************************************************/
static int MOVE_LIMIT = 10;

/* "nieskończoności" na potrzeby algorytmu alfabeta */
static const int POSITIVE_INFINITY = 10000000.0;
static const int NEGATIVE_INFINITY = -10000000.0;

/* Alfabeta ze sprawdzaniem losowych ruchów */
static MoveEvaluation alpha_beta_search(Board* b, symbol s, float alpha, float beta, int depth) {

	int boardSize;
	MoveEvaluation temp;
	Result result;
	int i;
	Move* moves;
	int moveCount, howManyChecked;
	Move tempMove;

	/* na początku ewaluujemy pas */
	
	MoveEvaluation bestValue;
	
	bestValue.move.x = -1;
	bestValue.move.y = -1;
	bestValue.eval = alpha;
	
	boardSize = board_size (b);

	moveCount = board_get_allowed_moves_count(b);

	/* jeśli nie poszukujemy dalej, to zwracamy wynik gry */
	if(moveCount == 0 || board_is_game_finished(b) || depth >= LIMIT) {
		
 	   	result = board_result(b);

		if(s == CIRCLE)
			temp.eval = result.circleResult-result.crossResult;
		else
			temp.eval = result.crossResult-result.circleResult;

		/* Jeżeli kończymy na nieparzystym poziomie, to */
		/* należy zmienić znak wyniku */
		if(depth % 2 == 1)
			temp.eval = -temp.eval;
		
	   return temp;
		
	}
	
	moves = board_get_allowed_moves(b);	

	howManyChecked = (moveCount < MOVE_LIMIT) ? moveCount : MOVE_LIMIT;

	for(i=0;i<howManyChecked;i++) {

		MoveEvaluation currentEval;

		/* losowanie, jeśli ograniczenie cokolwiek ogranicza */
		if(howManyChecked < moveCount)
			tempMove = moves[rand() % moveCount];
		/* Jeśli mieścimy się w limicie, próbujemy wszystkie */
		else
			tempMove = moves[i];
		
		board_play_move(b, tempMove);

		currentEval = alpha_beta_search(b, s, -beta, -alpha, depth+1);

		if(-currentEval.eval > alpha) {
			alpha = -currentEval.eval;
			bestValue.eval = alpha;
			bestValue.move = tempMove;
		}

		board_undo(b);

		if(alpha >= beta)
			break;

	}

	/* zwolnij pamięć i zwróć wartość */

	free(moves);
	
	return bestValue;
	
}

/* Zwraca najlepszy znaleziony ruch */
Move ai_find_best_move(Board* b) {

	MoveEvaluation ev;

	/* sprawdzaj maksymalnie 2/3 planszy */
	MOVE_LIMIT = board_size(b)*board_size(b)*2/3;

	/* początkowo (-Inf, +Inf) */
	ev = alpha_beta_search(b, board_get_current_player_symbol(b), NEGATIVE_INFINITY, POSITIVE_INFINITY, 0);

	return ev.move;
	
}

/* Zwraca wartość licytowaną */
/* losuje z zakresu KARA_ZA_GRUPĘ...2*KARA_ZA_GRUPĘ */
int ai_make_bid(int boardSize, int groupPenalty) {

	int bidValue = rand() % groupPenalty + 1;
	bidValue += groupPenalty;

	return bidValue;
	
}

/* Przewiduje wynik gry, podaje WYNIK_KÓŁKA - WYNIK_KRZYŻYKA */
float ai_predict_result(Board* b) {

	symbol s;
	MoveEvaluation ev;

	s = board_get_current_player_symbol(b);

	/* sprawdzaj maksymalnie 2/3 planszy */
	MOVE_LIMIT = board_size(b)*board_size(b)*2/3;

	/* początkowo (-Inf, +Inf) */
	ev = alpha_beta_search(b, s, NEGATIVE_INFINITY, POSITIVE_INFINITY, 0);

	return ev.eval;
	
}